
#import "DelugeWebJsonRpcClient.h"

//
// get torrent list request
//
@interface DelugeGetTorrentListRequest : DelugeWebRequest<GetTorrentDownloadListRequest>

//
// torrent list
//
@property(strong, nonatomic, readwrite) NSMutableArray*						torrentList;

//
// download rate
//
@property(assign, nonatomic, readwrite) uint64_t							downloadRate;

//
// upload rate
//
@property(assign, nonatomic, readwrite) uint64_t							uploadRate;

//
// total size
//
@property(assign, nonatomic, readwrite) uint64_t							totalSize;

@end

@implementation DelugeGetTorrentListRequest

//
// torrent list
//
@synthesize torrentList														= _torrentList;

//
// download rate
//
@synthesize downloadRate													= _downloadRate;

//
// upload rate
//
@synthesize uploadRate														= _uploadRate;

//
// total size
//
@synthesize totalSize														= _totalSize;

//
// init
//
- (id)initWithMethod:(NSString *)methodName
{
	//
	// call super
	//
	self																	= [super initWithMethod:methodName];
	if(!self)
		return nil;
	
	self.torrentList														= [NSMutableArray array];
	
	return self;
}

//
// dealloc
//
- (void)dealloc
{
	self.torrentList														= nil;
	
	[super dealloc];
}

@end

@implementation DelugeWebJsonRpcClient

//
// server info
//
@synthesize serverInfo														= _serverInfo;

//
// session id
//
@synthesize sessionId														= _sessionId;

//
// init with server info
//
- (id)initWithServerInfo:(ServerInfo*)serverInfo
{
	//
	// call super
	//
	self																	= [super init];
	if(!self)
		return nil;
	
	//
	// save server info
	//
	self.serverInfo															= serverInfo;
	
	return self;
}

//
// dealloc
//
- (void)dealloc
{
	self.serverInfo															= nil;
	self.sessionId															= nil;

	[super dealloc];
}

//
// setup request address
//
- (void)setupRequestAddress:(DelugeWebRequest*)delugeWebRequest
{
	NSURL* serverUrl														= [NSURL URLWithString:self.serverInfo.serverAddress];
	delugeWebRequest.protocol												= serverUrl.scheme;
	delugeWebRequest.domain													= serverUrl.host;
	delugeWebRequest.port													= [serverUrl.port integerValue];
	delugeWebRequest.relPath												= serverUrl.path;
	delugeWebRequest.sessionId												= self.sessionId;
}

//
// check connected
//
- (void)checkConnectedWithCallback:(TorrentClientFinishCallback)callback
{
	id checkConnectedCallback = ^void(DelugeWebRequest* checkConnectedRequest)
	{
		if(callback)
			callback(checkConnectedRequest);
		
		[checkConnectedRequest release];
	};
	
	DelugeWebRequest* checkConnectedRequest									= [[DelugeWebRequest alloc] initWithMethod:@"web.connected"];
	[self setupRequestAddress:checkConnectedRequest];
	[checkConnectedRequest startWithCallback:checkConnectedCallback];
}

//
// connect host
//
- (void)connectHost:(NSString*)hostId callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id connectHostCallback = ^void(DelugeWebRequest* connectHostRequest)
	{
		if(callback)
			callback(connectHostRequest);
		
		[connectHostRequest release];
	};
	
	DelugeWebRequest* connectHostRequest									= [[DelugeWebRequest alloc] initWithMethod:@"web.connect"];
	[connectHostRequest addParameter:hostId];
	[self setupRequestAddress:connectHostRequest];
	[connectHostRequest startWithCallback:connectHostCallback];
}

//
// check host status
//
- (void)checkHostStatus:(NSString*)hostId callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id checkHostStatusCallback = ^void(DelugeWebRequest* checkHostStatusRequest)
	{
		if(callback)
			callback(checkHostStatusRequest);
		
		[checkHostStatusRequest release];
	};
	
	DelugeWebRequest* checkHostStatusRequest								= [[DelugeWebRequest alloc] initWithMethod:@"web.get_host_status"];
	[checkHostStatusRequest addParameter:hostId];
	[self setupRequestAddress:checkHostStatusRequest];
	[checkHostStatusRequest startWithCallback:checkHostStatusCallback];
}

//
// get host list
//
- (void)getHostListWithCallback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];

	id checkHostStatusCallback = [[^void(DelugeWebRequest* checkHostStatusRequest)
	{
		if(checkHostStatusRequest.result)
		{
			if(![checkHostStatusRequest.result isKindOfClass:[NSArray class]])
				[checkHostStatusRequest setErrorMessage:@"Invalid result type" code:-1 domain:@"JsonRpcErrorDomain"];
			else if([checkHostStatusRequest.result count] < 4)
				[checkHostStatusRequest setErrorMessage:@"Invalid status info" code:-2 domain:@"JsonRpcErrorDomain"];
			else if([[checkHostStatusRequest.result objectAtIndex:3] isEqualToString:@"Offline"])
				[checkHostStatusRequest setErrorMessage:@"Host offline" code:-3 domain:@"JsonRpcErrorDomain"];
			else
				[self connectHost:[checkHostStatusRequest.result objectAtIndex:0] callback:callback];
		}
		
		if(checkHostStatusRequest.error && callback)
			callback(checkHostStatusRequest);
	} copy] autorelease];
	
	id getHostListCallback = ^void(DelugeWebRequest* getHostListRequest)
	{
		if(getHostListRequest.result)
		{
			if(![getHostListRequest.result isKindOfClass:[NSArray class]])
				[getHostListRequest setErrorMessage:@"Invalid result type" code:-4 domain:@"JsonRpcErrorDomain"];
			else if(![getHostListRequest.result count])
				[getHostListRequest setErrorMessage:@"Empty host list" code:-5 domain:@"JsonRpcErrorDomain"];
			else
				[self checkHostStatus:[[getHostListRequest.result objectAtIndex:0] objectAtIndex:0] callback:checkHostStatusCallback];
		}
		
		if(getHostListRequest.error && callback)
			callback(getHostListRequest);
				 
		[getHostListRequest release];
	};
	
	DelugeWebRequest* getHostListRequest									= [[DelugeWebRequest alloc] initWithMethod:@"web.get_hosts"];
	[self setupRequestAddress:getHostListRequest];
	[getHostListRequest startWithCallback:getHostListCallback];
}

//
// create session
//
- (void)createSessionWithCallback:(TorrentClientFinishCallback)callback
{
	id loginCallback = ^void(DelugeWebRequest* loginRequest)
	{
		self.sessionId														= loginRequest.sessionId;
		if(!loginRequest.error)
			[self checkConnectedWithCallback:callback];
		else if(callback)
			callback(loginRequest);

		[loginRequest release];
	};
	
	DelugeWebRequest* loginRequest											= [[DelugeWebRequest alloc] initWithMethod:@"auth.login"];
	self.sessionId															= nil;
	[loginRequest addParameter:self.serverInfo.password];
	[self setupRequestAddress:loginRequest];
	[loginRequest startWithCallback:loginCallback];
}

//
// login
//
- (void)loginWithCallback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id checkConnectedCallback = ^void(DelugeWebRequest* checkConnectedRequest)
	{
		if(checkConnectedRequest.error)
			[self createSessionWithCallback:callback];
		else if(![checkConnectedRequest.result boolValue])
			[self getHostListWithCallback:callback];
		else if(callback)
			callback(checkConnectedRequest);
	};
	
	if(self.sessionId)
		[self checkConnectedWithCallback:checkConnectedCallback];
	else
		[self createSessionWithCallback:callback];
}

//
// add torrent file
//
- (void)addTorrentFile:(NSString*)remoteFilePath autoStart:(BOOL)autoStart callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id addTorrentCallback = ^void(DelugeWebRequest* addTorrentRequest)
	{
		if(callback)
			callback(addTorrentRequest);
		
		[addTorrentRequest release];
	};
	
	DelugeWebRequest* addTorrentRequest										= [[DelugeWebRequest alloc] initWithMethod:@"web.add_torrents"];
	NSMutableDictionary* torrentInfo										= [NSMutableDictionary dictionaryWithCapacity:2];
	[torrentInfo setValue:remoteFilePath forKey:@"path"];
	[torrentInfo setValue:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:!autoStart] forKey:@"add_paused"] forKey:@"options"];
	[addTorrentRequest addParameter:[NSArray arrayWithObject:torrentInfo]];
	[self setupRequestAddress:addTorrentRequest];
	[addTorrentRequest startWithCallback:addTorrentCallback];
}

//
// download torrent file
//
- (void)downloadTorrent:(NSString*)torrentAddress callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id downloadTorrentCallback = [[^void(DelugeWebRequest* downloadTorrentRequest)
	{
		if(callback)
			callback(downloadTorrentRequest);
		
		[downloadTorrentRequest release];
	} copy] autorelease];
	
	id loginCallback = ^void(DelugeWebRequest* loginRequest)
	{
		if(!loginRequest.error)
		{
			DelugeWebRequest* downloadTorrentRequest						= [[DelugeWebRequest alloc] initWithMethod:@"web.download_torrent_from_url"];
			[downloadTorrentRequest addParameter:torrentAddress];
			[self setupRequestAddress:downloadTorrentRequest];
			[downloadTorrentRequest startWithCallback:downloadTorrentCallback];
		}
		else if(callback)
		{
			callback(loginRequest);
		}
	};
	
	[self loginWithCallback:loginCallback];
}

//
// add torrent link
//
- (void)addTorrent:(NSString*)torrentAddress autoStart:(BOOL)autoStart callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id downloadTorrentCallback = ^void(DelugeWebRequest* downloadTorrentRequest)
	{
		if(!downloadTorrentRequest.error)
			[self addTorrentFile:downloadTorrentRequest.result autoStart:autoStart callback:callback];
		else if(callback)
			callback(downloadTorrentRequest);
	};
	[self downloadTorrent:torrentAddress callback:downloadTorrentCallback];
}

//
// compute state
//
- (NSInteger)computeState:(NSString*)state
{
	if([state isEqualToString:@"Checking"])
		return kTorrentStateChecking;
	
	if([state isEqualToString:@"Downloading"] || [state isEqualToString:@"Seeding"])
		return kTorrentStateStarted;
	
	if([state isEqualToString:@"Paused"])
		return kTorrentStatePaused;
	
	if([state isEqualToString:@"Error"])
		return kTorrentStateError;
	
	if([state isEqualToString:@"Queued"])
		return kTorrentStateQueued;
	
	return 0;
}

//
// get torrent list
//
- (void)getTorrentListWithCallback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id getTorrentListCallback = [[^void(DelugeGetTorrentListRequest* getTorrentListRequest)
	{
		if(!getTorrentListRequest.error)
		{
			getTorrentListRequest.downloadRate								= [[getTorrentListRequest.result valueForKeyPath:@"stats.download_rate"] unsignedLongLongValue];
			getTorrentListRequest.uploadRate								= [[getTorrentListRequest.result valueForKeyPath:@"stats.upload_rate"] unsignedLongLongValue];
			NSDictionary* torrentList										= [getTorrentListRequest.result valueForKey:@"torrents"];
			if([torrentList isKindOfClass:[NSDictionary class]])
			{
				for(NSString* infoHash in torrentList)
				{
					NSDictionary* itemInfo									= [torrentList valueForKey:infoHash];
					TorrentDownloadItem* torrentInfo						= [[[TorrentDownloadItem alloc] init] autorelease];
					torrentInfo.torrentName									= [itemInfo valueForKey:@"name"];
					torrentInfo.infoHash									= infoHash;
					torrentInfo.size										= [[itemInfo valueForKey:@"total_size"] unsignedLongLongValue];
					torrentInfo.downloadedSize								= [[itemInfo valueForKey:@"total_done"] unsignedLongLongValue];
					torrentInfo.uploadedSize								= [[itemInfo valueForKey:@"total_uploaded"] unsignedLongLongValue];
					torrentInfo.downloadRate								= (uint64_t)[[itemInfo valueForKey:@"download_payload_rate"] doubleValue];
					torrentInfo.uploadRate									= (uint64_t)[[itemInfo valueForKey:@"upload_payload_rate"] doubleValue];
					torrentInfo.seederCount									= MAX([[itemInfo valueForKey:@"num_seeds"] integerValue], 0);
					torrentInfo.peerCount									= MAX([[itemInfo valueForKey:@"num_peers"] integerValue], 0);
					torrentInfo.totalSeederCount							= MAX([[itemInfo valueForKey:@"total_seeds"] integerValue], 0);
					torrentInfo.totalPeerCount								= MAX([[itemInfo valueForKey:@"total_peers"] integerValue], 0);
					torrentInfo.shareRatio									= MAX((NSInteger)([[itemInfo valueForKey:@"ratio"] doubleValue] * 1000), 0);
					torrentInfo.message										= [itemInfo valueForKey:@"tracker_status"];
					torrentInfo.leftTime									= torrentInfo.downloadedSize >= torrentInfo.size ? -1 : [[itemInfo valueForKey:@"eta"] integerValue];
					torrentInfo.state										= [self computeState:[itemInfo valueForKey:@"state"]];
					getTorrentListRequest.totalSize							+= torrentInfo.size;
					[getTorrentListRequest.torrentList addObject:torrentInfo];
				}
			}
		}
		
		if(callback)
			callback(getTorrentListRequest);
		
		[getTorrentListRequest release];
		
	} copy] autorelease];
	
	id loginCallback = ^void(DelugeWebRequest* loginRequest)
	{
		if(!loginRequest.error)
		{
			DelugeGetTorrentListRequest* getTorrentListRequest				= [[DelugeGetTorrentListRequest alloc] initWithMethod:@"web.update_ui"];
			NSMutableArray* keys											= [NSMutableArray arrayWithCapacity:10];
			[keys addObject:@"name"];
			[keys addObject:@"state"];
			[keys addObject:@"total_size"];
			[keys addObject:@"total_done"];
			[keys addObject:@"total_uploaded"];
			[keys addObject:@"download_payload_rate"];
			[keys addObject:@"upload_payload_rate"];
			[keys addObject:@"num_seeds"];
			[keys addObject:@"num_peers"];
			[keys addObject:@"total_seeds"];
			[keys addObject:@"total_peers"];
			[keys addObject:@"ratio"];
			[keys addObject:@"tracker_status"];
			[keys addObject:@"eta"];
			[getTorrentListRequest addParameter:keys];
			[getTorrentListRequest addParameter:[NSDictionary dictionary]];
			[self setupRequestAddress:getTorrentListRequest];
			[getTorrentListRequest startWithCallback:getTorrentListCallback];
		}
		else if(callback)
		{
			callback(loginRequest);
		}
	};
	
	[self loginWithCallback:loginCallback];
}

//
// update state
//
- (void)updateTorrentState:(TorrentDownloadItem*)torrentInfo callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id getTorrentStatusCallback = ^void(DelugeWebRequest* getTorrentStatusRequest)
	{
		if(!getTorrentStatusRequest.error)
			torrentInfo.state												= [self computeState:[getTorrentStatusRequest.result valueForKey:@"state"]];
		
		if(callback)
			callback(getTorrentStatusRequest);
		
		[getTorrentStatusRequest release];
	};
	
	DelugeWebRequest* getTorrentStatusRequest								= [[DelugeWebRequest alloc] initWithMethod:@"core.get_torrent_status"];
	[getTorrentStatusRequest addParameter:torrentInfo.infoHash];
	[getTorrentStatusRequest addParameter:[NSArray arrayWithObject:@"state"]];
	[self setupRequestAddress:getTorrentStatusRequest];
	[getTorrentStatusRequest startWithCallback:getTorrentStatusCallback];
}

//
// start torrent
//
- (void)startTorrent:(TorrentDownloadItem*)torrentInfo callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id startTorrentCallback = [[^void(DelugeWebRequest* startTorrentRequest)
	{
		if(startTorrentRequest.error)
			callback(startTorrentRequest);
		else
			[self updateTorrentState:torrentInfo callback:callback];
		
		[startTorrentRequest release];
		
	} copy] autorelease];
	
	id loginCallback = ^void(DelugeWebRequest* loginRequest)
	{
		if(!loginRequest.error)
		{
			DelugeWebRequest* startTorrentRequest							= [[DelugeGetTorrentListRequest alloc] initWithMethod:@"core.resume_torrent"];
			[startTorrentRequest addParameter:[NSArray arrayWithObject:torrentInfo.infoHash]];
			[self setupRequestAddress:startTorrentRequest];
			[startTorrentRequest startWithCallback:startTorrentCallback];
		}
		else if(callback)
		{
			callback(loginRequest);
		}
	};
	
	[self loginWithCallback:loginCallback];
}

//
// pause torrent
//
- (void)pauseTorrent:(TorrentDownloadItem*)torrentInfo callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id pauseTorrentCallback = [[^void(DelugeWebRequest* pauseTorrentRequest)
	{
		if(pauseTorrentRequest.error)
			callback(pauseTorrentRequest);
		else
			[self updateTorrentState:torrentInfo callback:callback];
		
		[pauseTorrentRequest release];

	} copy] autorelease];
	
	id loginCallback = ^void(DelugeWebRequest* loginRequest)
	{
		if(!loginRequest.error)
		{
			DelugeWebRequest* pauseTorrentRequest							= [[DelugeGetTorrentListRequest alloc] initWithMethod:@"core.pause_torrent"];
			[pauseTorrentRequest addParameter:[NSArray arrayWithObject:torrentInfo.infoHash]];
			[self setupRequestAddress:pauseTorrentRequest];
			[pauseTorrentRequest startWithCallback:pauseTorrentCallback];
		}
		else if(callback)
		{
			callback(loginRequest);
		}
	};
	
	[self loginWithCallback:loginCallback];
}

//
// stop torrent
//
- (void)stopTorrent:(TorrentDownloadItem*)torrentInfo callback:(TorrentClientFinishCallback)callback
{
	[self pauseTorrent:torrentInfo callback:callback];
}

//
// recheck torrent
//
- (void)recheckTorrent:(TorrentDownloadItem*)torrentInfo callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id recheckTorrentCallback = [[^void(DelugeWebRequest* recheckTorrentRequest)
	{
		if(recheckTorrentRequest.error)
			callback(recheckTorrentRequest);
		else
			[self updateTorrentState:torrentInfo callback:callback];
		
		[recheckTorrentRequest release];
		
	} copy] autorelease];
	
	id loginCallback = ^void(DelugeWebRequest* loginRequest)
	{
		if(!loginRequest.error)
		{
			DelugeWebRequest* recheckTorrentRequest							= [[DelugeGetTorrentListRequest alloc] initWithMethod:@"core.force_recheck"];
			[recheckTorrentRequest addParameter:[NSArray arrayWithObject:torrentInfo.infoHash]];
			[self setupRequestAddress:recheckTorrentRequest];
			[recheckTorrentRequest startWithCallback:recheckTorrentCallback];
		}
		else if(callback)
		{
			callback(loginRequest);
		}
	};
	
	[self loginWithCallback:loginCallback];

}

//
// remove torrent
//
- (void)removeTorrent:(TorrentDownloadItem*)torrentInfo eraseData:(BOOL)eraseData callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id removeTorrentCallback = [[^void(DelugeWebRequest* removeTorrentRequest)
	{
		if(callback)
			callback(removeTorrentRequest);
		
		[removeTorrentRequest release];
		
	} copy] autorelease];
	
	id loginCallback = ^void(DelugeWebRequest* loginRequest)
	{
		if(!loginRequest.error)
		{
			DelugeWebRequest* removeTorrentRequest							= [[DelugeGetTorrentListRequest alloc] initWithMethod:@"core.remove_torrent"];
			[removeTorrentRequest addParameter:torrentInfo.infoHash];
			[removeTorrentRequest addParameter:[NSNumber numberWithBool:eraseData]];
			[self setupRequestAddress:removeTorrentRequest];
			[removeTorrentRequest startWithCallback:removeTorrentCallback];
		}
		else if(callback)
		{
			callback(loginRequest);
		}
	};
	
	[self loginWithCallback:loginCallback];
}

@end
